package com.epam;

import java.util.Arrays;

public class MyLinkedList<E> implements MyList {

	int size = 0;

	private Node<E> first;
	private Node<E> last;

	public MyLinkedList(){

	}

	private boolean isElementIndex(int index) {
		return index >= 0 && index < size;
	}

	private void checkElementIndex(int index) {
		if (!isElementIndex(index))
			throw new IndexOutOfBoundsException();
	}

	private Node<E> node(int index) {
		Node<E> x = first;
		for (int i = 1; i < index; i++)
			x = x.next;
		return x;
	}

	private void linkFirst(Object e){
		@SuppressWarnings("unchecked")
		Node<E> newNode = new Node<E>(null,(E) e, first);
		first = newNode;
	}

	private static class Node<E> {
		E item;
		Node<E> next;
		Node<E> prev;

		Node(Node<E> prev, E element, Node<E> next) {
			this.item = element;
			this.next = next;
			this.prev = prev;
		}

		E getItem(){
			return item;
		}

		void setItem(E item){
			this.item = item;
		}
	}

	@Override
	public void add(Object e) {
		final Node<E> l = last;
		@SuppressWarnings("unchecked")
		final Node<E> newNode = new Node<E>(l, (E) e, null);
		last = newNode;
		if (l == null)
			first = newNode;
		else
			l.next = newNode;
		size++;
	}

	@SuppressWarnings("unchecked")
	@Override
	public void add(int index, Object e) {
		checkElementIndex(index);

		Node<E> lastNodeBeforeIndex = node(index);
		Node<E> firstNodeAfterIndex = lastNodeBeforeIndex.next;

		if (index == 0){
			linkFirst(e);
		}
		else{
			Node<E> newNode = new Node<E>(lastNodeBeforeIndex, (E) e, firstNodeAfterIndex);
			firstNodeAfterIndex.prev = newNode;
			lastNodeBeforeIndex.next = newNode;	
		}

		size++;
	}

	@Override
	public void addAll(Object[] c) {
		if (c.length == 0) throw new RuntimeException("can not add empty array");
		addAll(size, c);
	}

	@Override
	public void addAll(int index, Object[] c) {

		checkElementIndex(index);

		if (c.length == 0) throw new RuntimeException("can not add empty array");

		if (index == 0) {
			add(0, c[0]);
			index++;
			c = Arrays.copyOfRange(c, 1, c.length);
		}

		for(Object e:c){
			add(index++, e);
		}	
	}

	@Override
	public Object get(int index) {
		Node<E> x = node(index);  
		return x.getItem();
	}

	@Override
	public Object remove(int index) {

		checkElementIndex(index);

		Node<E> nodeToRemove = node(index);
		E item = first.item;
		size--;

		if (index == 0) {
			first = nodeToRemove.next;
			first.prev = null;
			return item;
		}else{
			Node<E> nodeBefore = node(index - 1);
			nodeBefore.next = nodeToRemove.next;
		}	
		return item;
	}

	@SuppressWarnings("unchecked")
	@Override
	public void set(int index, Object element) {
		node(index).setItem((E)element);
	}

	@Override
	public int indexOf(Object o) {
		for(int i = 0; i < size; i++){
			if (node(i).getItem().equals(o)) return i;
		}
		return -1;
	}

	@Override
	public int size() {
		return size;
	}

	@Override
	public Object[] toArray() {
		Object[] result = new Object[size];
		int i = 0;
		for (Node<E> x = first; x != null; x = x.next)
			result[i++] = x.item;
		return result;
	}

	@Override
	public String toString() {
		return Arrays.toString(toArray());
	}

}
